/*
 * Project CHEN Rendering Engine : /main.cpp
 * Created: 2020-06-3, Last modified:  2021-01-27
 * 
 * This is the main file.(For debugging only)
 * 
 * Copyright (C) 2021 Hineven (hineven@pku.edu.cn)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// ./src/chen --f src/data/crystal.obj --cam -250 0 20 -b -st --sample 0.3 0.3 --sens 8
//./src/chen --f src/data/crystal.obj --cam 0 10 -100 --sample 0.3 0.3 --sens 8

//./src/chen --f src/data/crystal.obj --cam 19 47 -60 --sample 0.15 0.15 --sens 32
//./src/chen --f src/data/crystal.obj --cam 19 37 -25 --sample 0.3 0.3 --sens 8
// ./chen --f data/crystal.obj --cam 19 37 -25 --sample 0.3 0.3 --sens 8
// center of world: 19 17 31


//./chen --f data/crystal.obj --cam 19 37 85 --sens 5

/*
    cmdline begin
    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.
*/

#include <GLFW/glfw3.h>
#include <thread>
#include "core/geometry.hpp"
#include "core/heads.hpp"
#include "shader/path.hpp"
#include "core/camera.hpp"
#include "core/material.hpp"
#include "core/scene.hpp"
#include "shape/sphere.hpp"
#include "shape/triangle.hpp"
#include "sampler/halton.hpp"
#include "light/area.hpp"
#include "structure/kdtree.hpp"
#include "tools/objreader.hpp"
#include "sampler/naive.hpp"
#include "shader/sppm.hpp"
#include "material/diffuse.hpp"
#include "core/bsdf.hpp"
#include <sys/time.h>
chen::Shader * shader;

chen::Vector3 cow(19, 17, 31);

bool finished = false;

const int HW = 256;

GLubyte img[HW*4][HW*4][3];

bool singlethread = false;

int render () {
    timeval t_s, t_e;
    shader->prepare(singlethread ? 1 : NCORES);
    gettimeofday(&t_s, nullptr);
    shader->render();
    gettimeofday(&t_e, nullptr);
    LOG(INFO) << "Render completed in " << (double)(t_e.tv_sec-t_s.tv_sec)+
        (double)(t_e.tv_usec-t_s.tv_usec)/1000000 << " seconds";
    finished = true;
    return 0;
}
int main(int argc, char**argv)
{
    

    google::InstallFailureSignalHandler();
    srand(10000);
    double swidth = 1, sheight = 1, sens = 1;
    double blx = 0, bly = 0, blz = 0;
    double trax = -20, tray = -15, traz = -20;
    double rotx = 0, roty = 0, rotz = 0;
    char fname[512];
    for(int i = 1; i<argc; i++) {
        if(strcmp(argv[i], "--sample") == 0) {
            sscanf(argv[++i], "%lf", &swidth);
            sscanf(argv[++i], "%lf", &sheight);
        }
        if(strcmp(argv[i], "--sens") == 0) {
            sscanf(argv[++i], "%lf", &sens);
        }
        if(strcmp(argv[i], "--cam") == 0) {
            sscanf(argv[++i], "%lf", &blx);
            sscanf(argv[++i], "%lf", &bly);
            sscanf(argv[++i], "%lf", &blz);
        }
        if(strcmp(argv[i], "--obj") == 0) {
            sscanf(argv[++i], "%lf", &trax);
            sscanf(argv[++i], "%lf", &tray);
            sscanf(argv[++i], "%lf", &traz);
        }
        if(strcmp(argv[i], "--rot") == 0) {
            sscanf(argv[++i], "%lf", &rotx);
            sscanf(argv[++i], "%lf", &roty);
            sscanf(argv[++i], "%lf", &rotz);
        }
        if(strcmp(argv[i], "--f") == 0) {
            sscanf(argv[++i], "%s", fname);
        }
        if(strcmp(argv[i], "-st") == 0) {
            singlethread = true;
        }
    }

    chen::NaiveSampler naive_sampler;
    //chen::HaltonSampler halton_sampler;
    chen::Sampler * sampler = &naive_sampler;
    // int cnts = 0;
    // for(int i = 0; i<1000000; i++) {
    //     chen::SurfInteract sif, si;
    //     Float pdf;
    //     sif.p.x = 8;
    //     chen::Sphere su(&chen::transform_unit, nullptr, 1.0f);
    //     su.uniformSample(sampler->gen2D(), &si, &pdf);
    //     chen::Ray r = sif.spawnRayTo(si.getOutside(false), sif.flipped);
    //     if(su.mask(r) && !chen::requal(r.t, 1)) {
    //         cnts ++;
    //     }
    // }
    // printf("%f\n", (Float)cnts/10000);

    // return 0;

    chen::Spectrum redsp(0.2, 0.8, 0.8);
    chen::Spectrum greensp(0.8, 0.15, 0.8);
    chen::Spectrum greenl(1, 3, 1);

    chen::GaussianFilter gausf(0.7, 0.15);

    chen::Film fi(HW, HW, &gausf, sens); // 设置相机机位
    chen::Transform ctr = chen::shift(chen::Vector3(blx, bly, blz)).reversed();
    //chen::Transform ctr = chen::shift(chen::Vector3(-3, 0, 0)).reversed();
    chen::BasicCamera cam(&fi, &ctr, M_PI/6, Infinity);

    std::vector<const chen::Shape*> slis;

    chen::ConstantTexture<chen::Spectrum> white_texture(0.9f);
    chen::ConstantTexture<chen::Spectrum> black_texture(0.15f);
    chen::ConstantTexture<chen::Spectrum> red_texture({0.9f, 0.3f, 0.3f});
    chen::ConstantTexture<chen::Spectrum> blue_texture({0.25f, 0.4f, 0.9f});

    chen::ChessboardTexture<chen::Spectrum> chessboard_texture(
        new chen::DirectUVMapping(10, 10, 0, 0),
        &white_texture, &black_texture
    );
    chen::ChessboardTexture<chen::Spectrum> chessboard_texture_rb(
        new chen::DirectUVMapping(25, 25, 0, 0),
        &red_texture, &blue_texture
    );
    chen::ConstantTexture<chen::Normal> no_offset_texture(chen::Normal(0, 0, 0));
    chen::DiffuseMaterial mat_diffuse(&no_offset_texture, &white_texture);
    chen::DiffuseMaterial mat_diffuse_chess(&no_offset_texture, &chessboard_texture);
    chen::DiffuseMaterial mat_diffuse_chess_rb(&no_offset_texture, &chessboard_texture_rb);
    
    chen::Transform trit = chen::shift(chen::Vector3(0+cow.x, 0+cow.y, 0+cow.z)).reversed();
    chen::Transform transsphere = chen::rotateX(0.5f).reversed()*chen::shift(chen::Vector3(-3+cow.x, 6+cow.y, 1+cow.z)).reversed();
    trit = trit*chen::rotateX(rotx).reversed()*chen::rotateY(roty).reversed()*chen::rotateZ(rotz).reversed();
    chen::Sphere crysphere(&transsphere, &mat_diffuse_chess, 6);
    chen::Transform translightball = chen::shift(chen::Vector3(6+cow.x, 4+cow.y, 6+cow.z)).reversed();
    chen::Sphere lightball(&translightball, &mat_diffuse, 2.5);
    slis.push_back(&crysphere);
    slis.push_back(&lightball);
    chen::Transform toplittrans1 = chen::shift(chen::Vector3(0+cow.x, 0+cow.y, 0+cow.z)).reversed();
    chen::Transform toplittrans2 = chen::shift(chen::Vector3(0+cow.x, 0+cow.y, 0+cow.z)).reversed();
    chen::Triangle toplightshape1(
        &toplittrans1, &mat_diffuse, chen::Point3(-3, 40, -3),
        chen::Point3(-3, 40, 3), chen::Point3(3, 40, -3)
    ); // 加载空中的光球mesh
    chen::Triangle toplightshape2(
        &toplittrans2, &mat_diffuse, chen::Point3(3, 40, 3),
        chen::Point3(-3, 40, 3), chen::Point3(3, 40, -3)
    ); // 加载空中的光球mesh

    slis.push_back(&toplightshape1);
    slis.push_back(&toplightshape2);
    
    //chen::Transform tlib = chen::shift(chen::Vector3(0+cow.x, 0+cow.y-7, 10+cow.z)).reversed();
    chen::loadTriangleMesh(&slis, "data/ground.obj", &trit, &mat_diffuse); // 加载地板
    //chen::loadTriangleMesh(&slis, "src/data/lib.obj", &tlib, &mat_diffuse);
    chen::Triangle grnd1(&trit, &mat_diffuse_chess_rb, {-20, 0, -20}, {-20, 0, 20}, {20, 0, -20});
    chen::Triangle grnd2(&trit, &mat_diffuse_chess_rb, {20, 0, 20}, {-20, 0, 20}, {20, 0, -20});
    slis.push_back(&grnd1);
    slis.push_back(&grnd2);

    chen::Kdtree strc(slis); // 构建加速数据结构


    // 加载光源
    std::vector<const chen::Light*> llis;
    chen::BasicLight backlight(chen::Spectrum(0.01f)); // 加载背景辐射光


    chen::DiffuseAreaLight toplight1(chen::Spectrum(1, 1, 1), (chen::Shape*)&toplightshape1, true); // j加载光球
    toplightshape1.arealight = &toplight1;
    toplightshape1.flags |= chen::FSHAPE_AREALIGHT;
    chen::DiffuseAreaLight toplight2(chen::Spectrum(1, 1, 1), (chen::Shape*)&toplightshape2, false); // j加载光球
    toplightshape2.arealight = &toplight2;
    toplightshape2.flags |= chen::FSHAPE_AREALIGHT;

    //chen::DiffuseAreaLight balllight(chen::Spectrum(1, 3, 1), (chen::Shape*)&lightball, false);
    //lightball.arealight = &balllight;
    //lightball.flags |= chen::FSHAPE_AREALIGHT;


    //llis.push_back((chen::Light*)&backlight);
    //llis.push_back((chen::Light*)&toplight1);
    llis.push_back((chen::Light*)&toplight2);
    //llis.push_back((chen::Light*)&balllight);

    //chen::DiffusePointLight pointlight(chen::Spectrum(200.0f), chen::Point3(cow.x+6, cow.y+8.505, cow.z+6));
    //llis.push_back(&pointlight);
    /*llis.push_back(&pointlight);
    llis.push_back(&pointlight2);*/
    chen::Scene scene(&strc, llis);

    //chen::PathShader s(&scene, &cam, &halton_sampler, 16, 16, 16);
    //chen::SimpleShader ss(&scene, &cam, sampler, 9);
    chen::SPPMShader sppm(&scene, &cam, sampler, 1, 4.0f, 1000000, 12, 5, 24);
    shader = &sppm;
    std::thread tr(render);

    GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return 0;


    
    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(HW*4, HW*4, "Renderer", nullptr, nullptr);
    if (!window)
    {
        glfwTerminate();
        return 0;
    }
    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    glViewport(0, 0, HW*4, HW*4);

    Float oscale = 1.0;
    
    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window))
    {
        /* Render here */
        //glClear(GL_COLOR_BUFFER_BIT);
#define TOBYTE(v) chen::clamp(0.f, 255.f, 255.f * chen::gammaCorrect(v) + 0.5f)
        glRasterPos2d(-1, -1);
        chen::Vector3 vcs(0);
        shader->camera->film->block();
        for(int i = 0; i<HW; i++)
            for(int j = 0; j<HW; j++) {
                chen::Vector3 output = shader->camera->film->RGB(i, j);
                vcs += output;
                for(int dx = 0; dx<4; dx++)
                    for(int dy = 0; dy<4; dy++) {
                        img[j*4+dx][(HW-i-1)*4+dy][0] = TOBYTE(std::min(1.0f, output.x/oscale));
                        img[j*4+dx][(HW-i-1)*4+dy][1] = TOBYTE(std::min(1.0f, output.y/oscale));
                        img[j*4+dx][(HW-i-1)*4+dy][2] = TOBYTE(std::min(1.0f, output.z/oscale));
                    }
            }
        shader->camera->film->release();
        glDrawPixels(HW*4, HW*4, GL_RGB, GL_UNSIGNED_BYTE, img);
#undef TOBYTE
        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
        usleep(250000);

    }

    glfwTerminate();

    return 0;
}

